/**
 * Principal classe do framework, que permite a seleção e manipulação 
 * simultânea de conjuntos de elementos. Na prática, você raramente irá 
 * instanciar manualmente uma classe "jProton.Set". Ao invés disso, é 
 * mais conveniente usar a função "jProton()", ou o atalho "$()", pois os dois 
 * códigos abaixo:
 * 
 * ~~
 * // Usa o namespace jProton para instanciar a classe jProton.Set
 * jProton(selector, context)
 * // Usa o namespace alternativo $ para instanciar a classe jProton.Set
 * $(selector, context)
 * ~~
 * 
 * equivalem a:
 *    
 * ~~
 * // Instancia explicitamente um novo objeto da classe jProton.Set 
 * new jProton.Set().select(selector, context)
 * ~~
 * @extends core
 * @class Principal classe do framework, que permite a seleção e manipulação 
 * simultânea de conjuntos de elementos.
 */
jProton.Set = function() {};

/**
 * Atalho para incluir funcionalidades na classe jProton.Set.
 * 
 * @type {jProton.Set}
 * @extends core
 */
jProton.set = jProton.Set.prototype;

// Atalho para jProton.set
var set = jProton.set;

// Adiciona a função extend para a classe jProton.Set
set.extend = jProton.extend;


// Extensão central da classe jProton.Set.
/**#@+ @extends core */
set.extend(/** @lends jProton.Set.prototype */ {

  '@extension': {'core': '0.3.0a'},

  /** 
   * Referência para o último conjunto da cadeia de operações.
   * 
   * @type jProton.Set
   */
  _lastSet: null,

  /** 
   * Versão do framework.
   * 
   * @type String
   */
  jproton: 'jProton.0.3.9a',

  /** 
   * Array do conjunto de elementos selecionados.
   * 
   * @type jProton.Set
   */
  e: [],
  
  /**
   * Método privado usado para ligar diferentes operações em cadeia. Será 
   * instanciado um novo objeto da classe "jProton.Set", vazio, ligado ao 
   * objeto atual pela referência do parâmetro "jProton.Set._lastSet". Caso 
   * seja informado algum array de elementos, o novo objeto será preenchido 
   * com esse conjunto de elementos.
   * 
   * @param {Array} [e] Array de elementos
   * @return {jProton.Set}
   */
  _chain: function(e) {
    var r = new jProton().copy(e);

    r._lastSet = this;

    return r;
  },

  /**
   * Adiciona os elementos que correspondem ao seletor informado. O parâmetro
   * seletor indica quais elementos serão selecionados, enquanto que o 
   * parâmetro contexto indica nos descendentes de qual elemento será será
   * feita a busca. Se nenhum contexto for informado, ele será considerado o
   * "document", ou seja, a busca ocorrerá em todo o documento. Opcionalmente,
   * pode-se passar um seletor como contexto, e nesse caso, o primeiro elemento
   * selecionado será usado como contexto.
   * 
   * @param {String}  selector  Seletor que descreve os elementos a serem 
   *                            escolhidos.
   * @param {Element} [context] Contexto usado na busca. O padrão é "document".
   * @return {jProton.Set}
   */
  select: function(selector, context) {
    var type = typeof selector;

    // processa $(expr)
    if (type == 'string') {
      this.e = this.e.concat(query.select(selector, context));
      return this;

    // processa $(obj)
    } else if (type == 'object') {

      // processa $(element)
      if (selector.nodeName) {
        this.e[this.e.length]= selector;

      // processa $(array-like)
      } else if (selector.length) {
        return this.copy(selector);
      }
    }

    return this; 
  },
  
  /**
   * Adiciona novos elementos ao conjunto.
   * 
   * @param {String}  selector   Seletor que descreve os elementos a serem 
   *                             adicionados.
   * @param {Element} [context]  Contexto usado na busca. O padrão é "document".
   * @return {jProton.Set}
   */
  add: function(selector, context) {
    return this._chain(this.e).select(selector, context);
  },

  /**
   * Procura por elementos dentro dos elementos selecionados. O conjunto atual
   * de elementos selecionados será usado como contexto de uma nova busca.
   * 
   * @param {String}  selector   Seletor que descreve os elementos a serem 
   *                             pesquisados.
   * @return {jProton.Set}
   */
  find: function(selector) {
    return this._chain().select(selector, this.e);
  },

  /**
   * Filtra os elementos selecionados deixando apenas os que satisfaçam ao 
   * seletor.
   * 
   * @param {String}  selector   Seletor que descreve os elementos a serem 
   *                             filtrados.
   * @return {jProton.Set}
   */
  filter: function(selector) {
    return this._chain().copy(query.filter(this.e, selector));
  },
  
  /**
   * Filtra os elementos selecionados removendo aqueles que satisfaçam ao 
   * seletor.
   * 
   * @param {String}  selector   Seletor que descreve os elementos a serem 
   *                             filtrados.
   * @return {jProton.Set}
   */
  not: function(selector) {
    return this._chain().copy(query.filter(this.e, selector, true));
  },
  
  /**
   * Procura pelo próximo irmão de cada elemento selecionado que satisfaça ao 
   * seletor.
   * 
   * @param {String}  selector   Seletor que descreve os elementos a serem 
   *                             encontrados.
   */
  next: function(selector){
    for(var i=0, l=this.e.length; i<l; i++)
      while((this.e[i] = this.e[i].nextSibling) && this.e[i].nodeType != 1);

    return selector ? this.filter(selector) : this;
  },

  /**
   * Procura pelo irmão anterior de cada elemento selecionado que satisfaça ao 
   * seletor.
   * 
   * @param {String}  selector   Seletor que descreve os elementos a serem 
   *                             encontrados.
   */
  prev: function(selector){
    for(var i=0, l=this.e.length; i<l; i++)
      while((this.e[i] = this.e[i].previousSibling) && this.e[i].nodeType != 1);

    return selector ? this.filter(selector) : this;
  },

  /**
   * Encerra a operação atual e retorna o último objeto "jProton.Set" utilizado
   * na cadeia.
   * 
   * @return {jProton.Set}
   */
  end: function() {
    return this._lastSet || new jProton.Set();
  },

  /**
   * Executa uma função para cada elemento selecionado.
   * 
   * ~~
   * // Exibe o conteúdo de cada elemento H1 existente no documento.
   * $('h1').each(function(i){
   *   alert(this.innerHTML);
   * });
   * ~~
   *    
   * @param {Function} fn  Função a ser executada para cada elemento 
   *                       selecionado. Essa função é chamada passando como
   *                       parâmetro o índice do elemento. O elemento por sua
   *                       vez é acessado pela palavra-chave "this".
   * @return {jProton.Set}
   */
  each: function(fn) {
    for(var i=0, e=this.e, ei; ei=e[i]; i++)
      fn.call(ei, i);

    return this;
  },

  /**
   * Copia todos elementos de um array.
   * 
   * @param {Array}  a  Array a ser copiado.
   * @return {jProton.Set}
   */
  copy: function(a) {
    if (a) {
      if (a.splice){
        this.e = this.e.concat(a)
  
      } else {
        for(var i=0, ai, e=this.e, el=e.length; ai=a[i]; i++)
          e[el++] = ai;
      }
    }
    return this;
  }

  /**#@-*/
});
